package com.lfy.kcat.user.template;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.lfy.kcat.user.consts.RedisConst;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author leifengyang
 * @version 1.0
 * @date 2025/9/19 14:02
 * @description:
 */
@Slf4j
@Component
public class RedisService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Qualifier("scheduledPoolExecutor")
    @Autowired
    ScheduledThreadPoolExecutor  scheduledPoolExecutor;


    /**
     * 从缓存中获取数据
     *
     * @param cacheKey
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T getData(String cacheKey, Class<T> tClass) {
        String json = redisTemplate.opsForValue().get(cacheKey);
        // 拿不到与拿到的是 x 都代表 null
        if (StringUtils.isEmpty(json)) {
            log.info("缓存未命中...");
            return null;
        }

        if (RedisConst.NULL_VALUE_PLACEHOLDER.equals(json)) {
            log.info("缓存命中，但是值为占位符，返回null");
            return null;
        }

        log.info("缓存命中，准备返回");
        T bean = JSONUtil.toBean(json, tClass);
        return bean;

    }


    /**
     * 保存数据
     *
     * @param cacheKey
     * @param data
     */
    public void saveData(String cacheKey, Object data) {
        // 0. 99999
        String string = RandomUtil.randomNumbers(5);
        int randomTTL = Integer.parseInt(string);


        if (data == null) {
            //TODO 为了防止缓存穿透攻击。即使空数据也缓存； 保存空值占位
            redisTemplate.opsForValue().set(cacheKey,
                RedisConst.NULL_VALUE_PLACEHOLDER,
                30 * 60 + randomTTL, TimeUnit.MINUTES);
            return;
        }
        String jsonStr = JSONUtil.toJsonStr(data);
        redisTemplate.opsForValue().set(cacheKey, jsonStr,
            RedisConst.DEFAULT_CACHE_TTL + randomTTL, TimeUnit.SECONDS);
    }

    /**
     * 保存数据
     *
     * @param cacheKey
     * @param data
     * @param timeout  秒为单位
     */
    public void saveData(String cacheKey, Object data, Long timeout) {
        if (data == null) {
            return;
        }
        String jsonStr = JSONUtil.toJsonStr(data);
        redisTemplate.opsForValue().set(cacheKey, jsonStr,
            timeout, TimeUnit.SECONDS);
    }


    /**
     * 实现延迟双删
     * @param cacheKey
     */
    public void deleteCache(String cacheKey) {
        //1、立即删
        redisTemplate.delete(cacheKey);

        //2、延迟删； 启动一个延迟任务（5~10s）再删除； 时间轮；
        //即使是双删，只要上次的读后更新缓存卡的够久（一定在删除后面）。双删那就没用


        // 1%； 后台系统有清缓存按钮。
        //最怕机器断电。
        scheduledPoolExecutor.schedule(()->{
            redisTemplate.delete(cacheKey);
        },10,TimeUnit.SECONDS);

    }


    public String lock(String key){
        String token = UUID.randomUUID().toString();
        //1、获取锁； setnx； 不存在就占坑； 返回true代表占坑成功，返回false失败
        //占锁 + 过期时间设置 = 原子操作
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, token,30,TimeUnit.SECONDS);
        while (!lock){
            //如果没
//            TimeUnit.MILLISECONDS.sleep(50);
            lock = redisTemplate.opsForValue().setIfAbsent(key, token,30,TimeUnit.SECONDS);
        }
        return token;
    }

    public void unlock(String key,String tokenValue){
        //删除锁的脚本
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
            "then\n" +
            "    return redis.call(\"del\",KEYS[1])\n" +
            "else\n" +
            "    return 0\n" +
            "end";


        /**
         * RedisScript<T> script,  脚本
         * List<K> keys,  传入key
         * Object... args
         */
        DefaultRedisScript redisScript = new DefaultRedisScript(script, Long.class);
        Long execute = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), tokenValue);
        if(execute==0){
            log.warn("差点删除了别人的锁....");
        }

    }
}
